package org.jeecg.modules.demo.gmslopedata.service.impl;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.errors.MinioException;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.IOUtils;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.shiro.SecurityUtils;
import org.apache.xmlbeans.XmlCursor;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.referencing.CRS;
import org.jeecg.common.api.dto.message.MessageDTO;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.WebsocketConst;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.MinioUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.oss.MinioConfig;
import org.jeecg.modules.demo.gmAdministrativeCode.service.IGmAdministrativeCodeService;
import org.jeecg.modules.demo.gmAlgorithmSetting.entity.GmAlgorithmSetting;
import org.jeecg.modules.demo.gmAlgorithmSetting.service.IGmAlgorithmSettingService;
import org.jeecg.modules.demo.gmIndexAdjustment.entity.GmIndexAdjustment;
import org.jeecg.modules.demo.gmIndexAdjustment.service.IGmIndexAdjustmentService;
import org.jeecg.modules.demo.gmhazardparameters.entity.GmHazardParameters;
import org.jeecg.modules.demo.gmhazardparameters.service.IGmHazardParametersService;
import org.jeecg.modules.demo.gmslopedata.controller.Utils;
import org.jeecg.modules.demo.gmslopedata.entity.EvaluateCountDTO;
import org.jeecg.modules.demo.gmslopedata.entity.GmSlopeData;
import org.jeecg.modules.demo.gmslopedata.mapper.GmSlopeDataMapper;
import org.jeecg.modules.demo.gmslopedata.service.IGmSlopeDataService;
import org.jeecg.modules.demo.gmslopedata1.entity.ImageData;
import org.jeecg.modules.demo.gmtreatmentmeasures.entity.GmTreatmentMeasures;
import org.jeecg.modules.demo.gmtreatmentmeasures.service.IGmTreatmentMeasuresService;
import org.jeecg.modules.demo.rockslopestablesetting.entity.RockSlopeStableSetting;
import org.jeecg.modules.demo.rockslopestablesetting.service.IRockSlopeStableSettingService;
import org.jeecg.modules.demo.sloperiskevaluationsetting.entity.SlopeRiskEvaluationSetting;
import org.jeecg.modules.demo.sloperiskevaluationsetting.service.ISlopeRiskEvaluationSettingService;
import org.jeecg.modules.demo.slopestablesetting.entity.SlopeStableSetting;
import org.jeecg.modules.demo.slopestablesetting.service.ISlopeStableSettingService;
import org.jeecg.modules.demo.soilrockslopesetting.entity.SoilRockSlopeSetting;
import org.jeecg.modules.demo.soilrockslopesetting.service.ISoilRockSlopeSettingService;
import org.jeecg.modules.message.websocket.WebSocket;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecgframework.poi.excel.ExcelExportUtil;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.TemplateWordConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgTemplateWordView;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * @Description: 边坡数据
 * @Author: jeecg-boot
 * @Date: 2025-06-21
 * @Version: V1.0
 */
@Service
@Slf4j
public class GmSlopeDataServiceImpl extends ServiceImpl<GmSlopeDataMapper, GmSlopeData> implements IGmSlopeDataService {

    private static final String CACHE_KEY_SLOPE = "config:slope_stable_setting";
    private static final String CACHE_KEY_ROCK_SLOPE = "config:rock_slope_stable_setting";
    private static final String CACHE_KEY_SOIL_ROCK_SLOPE = "config:soil_rock_slope_stable_setting";
    private static final String CACHE_KEY_SLOPE_RISK = "config:slope_risk_evaluation_setting";
    private static final String CACHE_KEY_HAZARD_PARAMETERS = "config:gm_hazard_parameters";
    private static final String CACHE_KEY_INDEX_ADJUSTMENT = "config:index_adjustment";






    @Value("${jeecg.minio.bucketName}")
    private String bucketName;
    @Value("${gm.deepLearningUrl}")
    private String deepLearningUrl;
    @Value("${gm.randomForestUrl}")
    private String randomForestUrl;
    @Value("${gm.linearRegressionUrl}")
    private String linearRegressionUrl;

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private ISlopeStableSettingService slopeStableSettingService;

    @Autowired
    private IRockSlopeStableSettingService rockSlopeStableSettingService;
    @Autowired
    private GmSlopeDataMapper gmSlopeDataMapper;
    @Autowired
    private IGmAdministrativeCodeService gmAdministrativeCodeService;

    @Autowired
    private ISoilRockSlopeSettingService soilRockSlopeSettingService;

    @Autowired
    private ISlopeRiskEvaluationSettingService slopeRiskEvaluationSettingService;
    @Autowired
    private IGmHazardParametersService gmHazardParametersService;
    @Autowired
    private IGmTreatmentMeasuresService gmTreatmentMeasuresService;
    @Autowired
    private IGmIndexAdjustmentService gmIndexAdjustmentService;
    @Autowired
    private ISysBaseAPI sysBaseAPI;
    @Autowired
    private IGmAlgorithmSettingService gmAlgorithmSettingService;

    @Autowired
    private WebSocket webSocket;
    // 注入MinIO客户端
//    @Autowired
//    private MinioClient minioClient;


    @Value("${jeecg.minio.minio_url}")
    private String uploadPrefix;

    /**
     * 通过excel导入数据
     *
     * @param request
     * @param response
     * @return
     */
    public Result<?> importExcelCustom(HttpServletRequest request, HttpServletResponse response, Class<GmSlopeData> clazz) {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
        for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
            MultipartFile file = entity.getValue();

            // 1. 严格校验文件格式（后缀+大小双重校验）
            String fileName = file.getOriginalFilename();
            if (fileName == null) {
                return Result.error("文件名称为空，请重新上传！");
            }
            // 校验文件后缀
            String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
            if (!"xls".equals(fileExt) && !"xlsx".equals(fileExt)) {
                return Result.error("文件格式错误，仅支持 .xls 和 .xlsx 格式！");
            }
            // 校验文件大小（避免空文件或无效文件，可根据需求调整大小阈值）
            if (file.getSize() <= 0 || file.getSize() > 1024 * 1024 * 10) { // 10MB上限
                return Result.error("文件大小异常，请上传 0-10MB 范围内的Excel文件！");
            }

            ImportParams params = new ImportParams();
//            params.setTitleRows(2); // 需确保与Excel实际标题行数一致
            params.setHeadRows(1);  // 需确保与Excel实际表头行数一致
            params.setNeedSave(true);

            try {
                List<GmSlopeData> list;
                InputStream inputStream = file.getInputStream();

                // 2. 尝试重置流位置（若流支持标记）
                if (inputStream.markSupported()) {
                    inputStream.mark(0); // 标记流起始位置
                    list = ExcelImportUtil.importExcel(inputStream, clazz, params);
                    inputStream.reset(); // 重置流（非必需，仅为后续可能的操作预留）
                } else {
                    // 3. 流不支持重置时，保存为本地临时文件再读取
                    File tempFile = File.createTempFile("excel-import-", "." + fileExt);
                    file.transferTo(tempFile); // 将MultipartFile写入临时文件
                    list = ExcelImportUtil.importExcel(new FileInputStream(tempFile), clazz, params);
                    tempFile.deleteOnExit(); // JVM退出时自动删除临时文件
                }

                // 按（town, village）分组，统计每组需要新增的数量
                Map<String, Long> groupCountMap = new HashMap<>();
                for (GmSlopeData data : list) {
                    String key = data.getTown() + "_" + data.getVillage() + "_A"; // 组合键：town_village_type
                    groupCountMap.put(key, groupCountMap.getOrDefault(key, 0L) + 1);
                }

                // 预查询每组当前最大序号，并计算起始序号
                Map<String, Long> startIndexMap = new HashMap<>();
                for (String key : groupCountMap.keySet()) {
                    String[] parts = key.split("_");
                    String town = parts[0];
                    String village = parts[1];
                    String type = parts[2];

                    // 查询当前已存在的数量
                    Long currentCount = gmSlopeDataMapper.selectCount(new LambdaQueryWrapper<GmSlopeData>()
                            .eq(GmSlopeData::getTown, town)
                            .eq(GmSlopeData::getVillage, village)
                            .eq(GmSlopeData::getType, type));
                    // 起始序号 = 当前数量 + 1
                    startIndexMap.put(key, currentCount + 1);
                }

                // 为每条数据分配序号（按组递增）
                Map<String, Long> currentIndexMap = new HashMap<>(startIndexMap); // 复制起始序号作为当前序号
                for (GmSlopeData data : list) {
                    String key = data.getTown() + "_" + data.getVillage() + "_A";
                    Long index = currentIndexMap.get(key);
                    data.setNumber(buildNumber(data, index)); // 生成编号
                    currentIndexMap.put(key, index + 1); // 序号递增
                    data.setFlag("1");
                    data.setIsUpdate("0");
                    data.setType("A");
                    data.setUpdateTime(new Date());
                    calculateYValues(data);
                }
                long start = System.currentTimeMillis();
                this.saveBatch(list);
                log.info("批量插入消耗时间：" + (System.currentTimeMillis() - start) + "毫秒，数据行数：" + list.size());

                return Result.ok("文件导入成功！数据行数：" + list.size());
            } catch (Exception e) {
                String msg = e.getMessage();
                log.error("文件导入失败：", e); // 打印完整堆栈，便于排查详细错误
                if (msg != null && msg.contains("Duplicate entry")) {
                    return Result.error("文件导入失败：存在重复数据！");
                } else if (msg != null && msg.contains("不识别该文件")) {
                    return Result.error("文件导入失败：Excel文件格式损坏或无法识别，请检查文件完整性！");
                } else {
                    return Result.error("文件导入失败：" + (msg == null ? "未知错误" : msg));
                }
            }
        }
        return Result.error("文件导入失败：未获取到上传的文件！");
    }

    // 单独的编号生成方法
    private String buildNumber(GmSlopeData data, Long index) {
        String code = gmAdministrativeCodeService.getCodeByPosition(data.getTown(), data.getVillage());
        return code + "A" + String.format("%04d", index);
    }
    private String getNumber(GmSlopeData gmSlopeData){
        String town = gmSlopeData.getTown();
        String village = gmSlopeData.getVillage();
//        queryWrapper.eq(GmSlopeData::getTown, town)
//                .eq(GmSlopeData::getVillage, village);
        Long count = gmSlopeDataMapper.selectCount(new LambdaQueryWrapper<GmSlopeData>()
                .eq(GmSlopeData::getTown, town)
                .eq(GmSlopeData::getType, "A")
                .eq(GmSlopeData::getVillage, village));
        String code = gmAdministrativeCodeService.getCodeByPosition(town, village);
        System.out.println("code = " + code);
        // 格式化为4位，不足补零
        String formattedCount = String.format("%04d", count+1);
        String number = code +'A'+formattedCount;
        return number;
    }


    /**
     * 导出审批意见Word（模板）
     *
     * @param id           ID
     * @param templatePath 模板地址路径
     * @return
     */

    @Override
    public ModelAndView exportWord(String id, String templatePath) {
        // 设置模板路径为本地路径
        templatePath = "E:\\项目资料\\项目资料\\边坡\\简易设计模板3.docx";
        System.out.println("templatePath = " + templatePath);
        ModelAndView mv = null;
        try {
            // Step.1、判断模板文件是否存在
            InputStream resource = getClass().getClassLoader().getResourceAsStream(templatePath);

//            File templateFile = new File();

            Map<String, Object> dataMap = new HashMap<>();
            Map<String, Object> imageMap = new HashMap<>();
            int imagesCount = 0;
            extracted(id, dataMap, imageMap, imagesCount);
            // Step.3、封装模板数据

            // 生成 Word 文档
            byte[] wordBytes = generateWordDocumentBytes(templatePath, dataMap, imageMap, imagesCount);

            // 保存到本地
            Files.write(Paths.get("D:/output/请假审批单.docx"), wordBytes);

            System.out.println("生成成功！");

            // Step.6、如果仍需返回 ModelAndView 给前端，则继续构造
            mv = new ModelAndView(new JeecgTemplateWordView());
            mv.addObject(TemplateWordConstants.FILE_NAME, "请假审批单");
            mv.addObject(TemplateWordConstants.URL, templatePath);
            mv.addObject(TemplateWordConstants.MAP_DATA, dataMap);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return mv;
    }


//    // 生成Excel字节流
//    public byte[] exportExcel(List<GmSlopeData> dataList) {
//        ExportParams exportParams = new ExportParams("边坡数据", "数据列表");
//        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, GmSlopeData.class, dataList);
//        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
//            workbook.write(os);
//            return os.toByteArray();
//        } catch (Exception e) {
//            throw new RuntimeException("Excel导出失败", e);
//        }
//    }
//
//    public void exportZip(List<GmSlopeData> dataList, OutputStream zipOs) {
//        try (ZipOutputStream zos = new ZipOutputStream(zipOs)) {
//            // 1. 添加Excel文件到ZIP
//            byte[] excelBytes = exportExcel(dataList);
//            ZipEntry excelEntry = new ZipEntry("数据汇总.xlsx");
//            zos.putNextEntry(excelEntry);
//            zos.write(excelBytes);
//            zos.closeEntry();
//
//            // 2. 添加图片文件到ZIP的“图片文件”目录
//            for (GmSlopeData data : dataList) {
//                String dataId = data.getNumber(); // 用数据ID区分不同记录的图片
//                // 处理单个图片字段（示例：frontPhoto）
//                addImageToZip(zos, dataId, "frontPhoto", data.getFrontPhoto());
//                // 同理处理其他图片字段：behindPhoto、leftPhoto、rightPhoto等
//                addImageToZip(zos, dataId, "behindPhoto", data.getBehindPhoto());
//
//            }
//        } catch (Exception e) {
//            throw new RuntimeException("ZIP打包失败", e);
//        }
//    }





//    /**
//     * 从MinIO读取图片（路径格式：gmslopedata/image/20250802/xxx.jpg）并写入ZIP
//     * @param zos ZIP输出流
//     * @param dataId 数据ID（用于图片命名）
//     * @param fieldName 图片字段名（如frontPhoto）
//     * @param objectKey MinIO中的对象键（即实体类中的图片路径，如gmslopedata/image/20250802/xxx.jpg）
//     */
//    private void addImageToZip(ZipOutputStream zos, String dataId, String fieldName, String objectKey) throws Exception {
//        if (objectKey == null || objectKey.trim().isEmpty()) {
//            return; // 图片路径为空，跳过
//        }
//        String[] split = objectKey.split(",");
//        for (String key : split) {
//            // 从MinIO获取图片输入流
//            try (InputStream minioInputStream = minioClient.getObject(
//                    GetObjectArgs.builder()
//                            .bucket(bucketName)  // 固定桶名
//                            .object(key)   // 实体类中的图片路径即对象键
//                            .build()
//            )) {
//                // 提取文件后缀（如".jpg"，从objectKey中获取）
//                String fileSuffix = getFileSuffix(key);
//                // 生成ZIP中的图片路径（避免重名：数据ID_字段名.后缀）
//                String zipImagePath = dataId+"/" + fieldName + fileSuffix;
//
//                // 写入ZIP
//                ZipEntry imageEntry = new ZipEntry(zipImagePath);
//                zos.putNextEntry(imageEntry);
//
//                byte[] buffer = new byte[1024 * 4]; // 4KB缓冲，提升效率
//                int len;
//                while ((len = minioInputStream.read(buffer)) != -1) {
//                    zos.write(buffer, 0, len);
//                }
//                zos.closeEntry();
//
//            } catch (Exception e) {
//                // 打印错误日志，不中断整个导出流程
//                System.err.println("读取MinIO图片失败，桶名：" + bucketName + "，对象键：" + key + "，错误：" + e.getMessage());
//            }
//        }
//
//    }

    /**
     * 从MinIO对象键中获取文件后缀（如"123/front.jpg" -> ".jpg"）
     */
    private String getFileSuffix(String objectKey) {
        int lastDotIndex = objectKey.lastIndexOf(".");
        if (lastDotIndex == -1) {
            return ""; // 无后缀
        }
        return objectKey.substring(lastDotIndex);
    }



    public byte[] exportWord1(String id, String templatePath) {
        // 设置模板路径为本地路径
//        templatePath="templates/templates.docx";
//        System.out.println("templatePath = " + templatePath);
        try {
            // Step.1、判断模板文件是否存在
            File templateFile = new File(templatePath);
            if (!templateFile.exists()) {
                throw new IOException("模板文件不存在:" + templatePath);
            }
            Map<String, Object> dataMap = new HashMap<>();
            Map<String, Object> imageMap = new HashMap<>();
            //设置imageCount为全局变量
            int imageCount = 0;
            imageCount  =extracted(id, dataMap, imageMap,imageCount);
            // Step.3、封装模板数据
//            System.out.println("imageCount = " + imageCount);
            // 生成 Word 文档
            byte[] wordBytes = generateWordDocumentBytes(templatePath, dataMap, imageMap,imageCount);

            // 保存到本地
//            Files.write(Paths.get("D:/output/请假审批单.docx"), wordBytes);

          return wordBytes;


        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    @Override
    public Map<String, Object> getEvaluateStatistics(GmSlopeData gmSlopeData, Map<String, String[]> requestParams) {
        // 1. 构建查询条件 QueryWrapper
        Map<String, QueryRuleEnum> customRuleMap = new HashMap<>();
        customRuleMap.put("county", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("town", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("village", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("team", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("patchNumber", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("position", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("threatHouse", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("householder", QueryRuleEnum.LIKE_WITH_OR);
        customRuleMap.put("obligee", QueryRuleEnum.LIKE_WITH_OR);

        QueryWrapper<GmSlopeData> queryWrapper = QueryGenerator.initQueryWrapper(gmSlopeData, requestParams, customRuleMap);

        // 2. 应用数据权限
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        String roleCode = sysUser.getRoleCode();
        QueryWrapper<GmSlopeData> gmSlopeDataQueryWrapper = buildBaseQueryWrapper(roleCode, sysUser);
        // 3. 统计总数
        long totalCount = this.count(gmSlopeDataQueryWrapper); // 使用ServiceImpl提供的count方法

        // 统计 result1 不为空的数量
        QueryWrapper<GmSlopeData> queryWrapper1 = this.buildBaseQueryWrapper(roleCode, sysUser);
        queryWrapper1.isNotNull("result");
        long countResult = gmSlopeDataMapper.selectCount(queryWrapper1);
        System.out.println("result 不为空的数量: " + countResult);

        // 统计 result2 不为空的数量
        QueryWrapper<GmSlopeData> queryWrapper2 = this.buildBaseQueryWrapper(roleCode, sysUser);
        queryWrapper2.isNotNull("result2");
        long countResult2 = gmSlopeDataMapper.selectCount(queryWrapper2);
        System.out.println("result2 不为空的数量: " + countResult2);

        // 统计 result3 不为空的数量
        QueryWrapper<GmSlopeData> queryWrapper3 = this.buildBaseQueryWrapper(roleCode, sysUser);
        queryWrapper3.isNotNull("result3");
        long countResult3 = gmSlopeDataMapper.selectCount(queryWrapper3);
        System.out.println("result3 不为空的数量: " + countResult3);





        if (totalCount == 0) {
            Map<String, Object> emptyResult = new HashMap<>();
            emptyResult.put("total", 0);
            emptyResult.put("resultStatistics", buildEmptyStatisticsMap());
            emptyResult.put("result2Statistics", buildEmptyStatisticsMap());
            emptyResult.put("result3Statistics", buildEmptyStatisticsMap());
            emptyResult.put("accuracy", Map.of("result", 0.0, "result2", 0.0, "result3", 0.0));
            return emptyResult;
        }

        // 4. 执行分组统计查询
        // 使用 baseMapper 来调用我们自定义的Mapper方法
        List<Map<String, Object>> statisticsList = gmSlopeDataMapper.statisticsByQueryWrapper(queryWrapper);

        // 5. 聚合统计结果
        Map<String, Integer> resultStats = buildEmptyStatisticsMap();
        Map<String, Integer> result2Stats = buildEmptyStatisticsMap();
        Map<String, Integer> result3Stats = buildEmptyStatisticsMap();
        int resultAccurateCount = 0;
        int result2AccurateCount = 0;
        int result3AccurateCount = 0;

        for (Map<String, Object> item : statisticsList) {
            String result = (String) item.get("result");
            String result2 = (String) item.get("result2");
            String result3 = (String) item.get("result3");
            String artificialStability = (String) item.get("artificialStability");
            Long count = (Long) item.get("count");
            int countInt = count.intValue();

            updateStatisticsMap(resultStats, result, countInt);
            updateStatisticsMap(result2Stats, result2, countInt);
            updateStatisticsMap(result3Stats, result3, countInt);

            if (isPredictionAccurate(result, artificialStability)) {
                resultAccurateCount += countInt;
            }
            if (isPredictionAccurate(result2, artificialStability)) {
                result2AccurateCount += countInt;
            }
            if (isPredictionAccurate(result3, artificialStability)) {
                result3AccurateCount += countInt;
            }
        }

//        // 6. 计算准确率
        double resultAccuracy = calculateAccuracy(resultAccurateCount, countResult);
        double result2Accuracy = calculateAccuracy(result2AccurateCount, countResult2);
        double result3Accuracy = calculateAccuracy(result3AccurateCount, countResult3);

        // 7. 组装最终返回结果
        Map<String, Object> finalResult = new HashMap<>();
        finalResult.put("total", totalCount);
        finalResult.put("result1", resultStats);
        finalResult.put("result2", result2Stats);
        finalResult.put("result3", result3Stats);
        finalResult.put("accuracy", Map.of(
                "result", resultAccuracy,
                "result2", result2Accuracy,
                "result3", result3Accuracy
        ));

        return finalResult;
    }

    private QueryWrapper<GmSlopeData> buildBaseQueryWrapper(String roleCode, LoginUser sysUser) {
        QueryWrapper<GmSlopeData> queryWrapper = new QueryWrapper<>();

        // 1. 添加基于 roleCode 的数据权限条件
        switch (roleCode) {
            case "county":
                queryWrapper.eq("county", sysUser.getCounty());
                break;
            case "town":
                queryWrapper.eq("county", sysUser.getCounty());
                queryWrapper.eq("town", sysUser.getTown());
                break;
            case "village":
                queryWrapper.eq("create_by", sysUser.getUsername());
                break;
            // 可以根据需要添加 default 处理
        }

        // 2. 添加公共的 flag = 1 条件
        queryWrapper.eq("flag", "1");

        return queryWrapper;
    }

    // --- 以下是私有辅助方法 ---

    private Map<String, Integer> buildEmptyStatisticsMap() {
        Map<String, Integer> map = new HashMap<>();
        map.put("稳定", 0);
        map.put("基本稳定", 0);
        map.put("欠稳定", 0);
        map.put("不稳定", 0);
        return map;
    }

    private void updateStatisticsMap(Map<String, Integer> statsMap, String result, int count) {
        if (result == null) {
            return;
        }
        switch (result) {
            case "稳定":
                statsMap.put("稳定", statsMap.get("稳定") + count);
                break;
            case "基本稳定":
                statsMap.put("基本稳定", statsMap.get("基本稳定") + count);
                break;
            case "欠稳定":
                statsMap.put("欠稳定", statsMap.get("欠稳定") + count);
                break;
            case "不稳定":
                statsMap.put("不稳定", statsMap.get("不稳定") + count);
                break;
        }
    }

    private boolean isPredictionAccurate(String prediction, String artificialStability) {
        return prediction != null && artificialStability != null && prediction.equals(artificialStability);
    }

    private double calculateAccuracy(int accurateCount, long totalCount) {
        if (totalCount == 0) {
            return 0.0;
        }
        return Math.round(((double) accurateCount / totalCount) * 10000) / 100.0;
    }

    private int extracted(String id, Map<String, Object> dataMap, Map<String, Object> imageMap, int imagesCount) throws Exception {
        GmSlopeData gmSlopeData = this.getById(id);
        dataMap.putIfAbsent("position", gmSlopeData.getPosition());
        dataMap.putIfAbsent("county", gmSlopeData.getCounty());
        dataMap.putIfAbsent("town", gmSlopeData.getTown());
        dataMap.putIfAbsent("village", gmSlopeData.getVillage());
        dataMap.putIfAbsent("team", gmSlopeData.getTeam());
        dataMap.putIfAbsent("number", gmSlopeData.getNumber());
        dataMap.putIfAbsent("latitude", gmSlopeData.getLatitude());
        dataMap.putIfAbsent("longitude", gmSlopeData.getLongitude());
        dataMap.putIfAbsent("slopeType", queryDictItemsByCode("slope_type", gmSlopeData.getSlopeType()));
        dataMap.putIfAbsent("naturalSlopeGradient", queryDictItemsByCode("natural_slope_gradient", gmSlopeData.getNaturalSlopeGradient()));
        dataMap.putIfAbsent("rockType", queryDictItemsByCode("rock_code", gmSlopeData.getRockType()));
        dataMap.putIfAbsent("cutSlopeTime", queryDictItemsByCode("cut_slope_time", gmSlopeData.getCutSlopeTime()));
        dataMap.putIfAbsent("cutSlopeHeight", queryDictItemsByCode("cut_slope_height", gmSlopeData.getCutSlopeHeight()));
        dataMap.putIfAbsent("cutSlopeWidth", queryDictItemsByCode("cut_slope_width", gmSlopeData.getCutSlopeWidth()));
        dataMap.putIfAbsent("vegetationCoverage", queryDictItemsByCode("vegetation_coverage", gmSlopeData.getVegetationCoverage()));
        dataMap.putIfAbsent("cutSlopeShape", queryDictItemsByCode("cut_slope_shape", gmSlopeData.getCutSlopeShape()));
        dataMap.putIfAbsent("slopeWallDistance", queryDictItemsByCode("slope_wall_distance", gmSlopeData.getSlopeWallDistance()));
        dataMap.putIfAbsent("result", gmSlopeData.getResult());
        dataMap.putIfAbsent("cutSlopeGradient", queryDictItemsByCode("cut_slope_gradient", gmSlopeData.getCutSlopeGradient()));
        dataMap.putIfAbsent("threatHouse", queryDictItemsByCode("threat_house", gmSlopeData.getThreatHouse()));
//        dataMap.putIfAbsent("resident", gmSlopeData.getResident().equals("A") ? "是" : "否");
//        dataMap.putIfAbsent("threatenedHouseholds", gmSlopeData.getThreatenedHouseholds());
//        dataMap.putIfAbsent("threatPopulation", gmSlopeData.getThreatPopulation());

//        Map<String, Object> map = queryByPoint(gmSlopeData.getLatitude(), gmSlopeData.getLongitude());
//        dataMap.putIfAbsent("surface", map.get("SYMBOL"+"DESCRIPTOR"));

//        int threatPopulation = Integer.parseInt(gmSlopeData.getThreatPopulation());
//        if (threatPopulation > 100) {
//            dataMap.putIfAbsent("harm", "大型");
//        } else if (threatPopulation > 10) {
//            dataMap.putIfAbsent("harm", "中型");
//        } else {
//            dataMap.putIfAbsent("harm", "小型");
//        }
        switch (gmSlopeData.getResult()){
            case "不稳定":
                dataMap.putIfAbsent("description",
                        "且切坡前缘临空，坡度"+queryDictItemsByCode("cut_slope_gradient", gmSlopeData.getCutSlopeGradient())+"，在长时间强降雨条件下，极易发生边坡失稳");
                break;
                case "稳定":
                    dataMap.putIfAbsent("description",
                            "但切坡前缘临空，坡度"+queryDictItemsByCode("cut_slope_gradient", gmSlopeData.getCutSlopeGradient())+"，在长时间强降雨条件下，仍存在发生边坡失稳的可能");
                    break;
                default:
                    dataMap.putIfAbsent("description",
                            "但切坡前缘临空，坡度"+queryDictItemsByCode("cut_slope_gradient", gmSlopeData.getCutSlopeGradient())+"，在长时间强降雨条件下，较易发生边坡失稳");
                    break;
        }



        // 安全转换威胁人口
        String threatPopulationStr = gmSlopeData.getThreatPopulation();
        String threatenedHouseholds = gmSlopeData.getThreatenedHouseholds();
        int threatPopulation = 0;

        try {
            if (threatPopulationStr != null && !threatPopulationStr.trim().isEmpty()) {
                threatPopulation = Integer.parseInt(threatPopulationStr);
            }
        } catch (NumberFormatException e) {
            log.error("威胁人口转换为数字失败: {}", threatPopulationStr, e);
        }

// 确定危害等级
        String harmLevel = "未知";
        if (threatPopulation > 100) {
            harmLevel = "大型";
        } else if (threatPopulation > 10) {
            harmLevel = "中型";
        } else if (threatPopulation > 0) {
            harmLevel = "小型";
        }
        dataMap.putIfAbsent("harm", harmLevel);

        //是否有人居住
        if (gmSlopeData.getResident().equals("A")) {
            dataMap.putIfAbsent("resident", "有人居住，威胁人口"+threatenedHouseholds+'户'+threatPopulation+"人");
        }else
        {
            dataMap.putIfAbsent("resident", "无人居住");
        }

//        未做治理措施（已有治理措施，采用了截排水沟、护坡墙、挡土墙、综合治理）。
        if (gmSlopeData.getGovernanceMeasures().equals("D")) {
            dataMap.putIfAbsent("governanceMeasures", "未做治理措施");
        } else {
            String[] split = gmSlopeData.getGovernanceMeasures().split(",");
            StringBuilder builder = new StringBuilder();
            for (String s : split) {
                builder.append(queryDictItemsByCode("governance_measures", s)+"、");
            }
            builder.deleteCharAt(builder.length()-1);
            dataMap.putIfAbsent("governanceMeasures", "已有治理措施，采用了" + builder.toString());
        }
        String content = "";
        if (gmSlopeData.getSlopeType().equals("A")) {
            String rockChar = "A".equals(gmSlopeData.getRockCharacteristics()) ?
                    "岩层无顺向结构面及软弱夹层" : "岩层有顺向结构面及软弱夹层";
            String fractureDev = "A".equals(gmSlopeData.getFractureDevelopmentDegree()) ?
                    "节理裂隙不发育" : "节理裂隙发育";
            content = "岩质切坡，出露强风化岩层，" + rockChar + "，" + fractureDev;

        } else if (gmSlopeData.getSlopeType().equals("C")) {
//            切坡类型为土质切坡，土体类型为砂质土，土层呈密实状态
            String soilLooseness;
            switch (gmSlopeData.getSoilLooseness()) {
                case "A":
                    soilLooseness = "粘性土";
                    break;
                case "B":
                    soilLooseness = "砂质土";
                    break;
                case "C":
                    soilLooseness = "碎石土";
                    break;
                default:
                    soilLooseness = "未知土质"; // 默认情况
                    break;
            }
            String overlyingSoilType = "A".equals(gmSlopeData.getOverlyingSoilType()) ? "土层呈密实状态" : "土层呈松散状态";
            content = "土质切坡，土体类型为" + soilLooseness + "，" + overlyingSoilType;
        } else {
//            dataMap.putIfAbsent("content", gmSlopeData.getMeasure());
//            岩土混合切坡，上部土体类型为砂质土，土层呈密实状态；下部出露强风化岩层，岩层无顺向结构面及软弱夹层，节理裂隙不发育
            String soilLooseness;
            switch (gmSlopeData.getSoilLooseness()) {
                case "A":
                    soilLooseness = "上部土体类型为粘性土";
                    break;
                case "B":
                    soilLooseness = "上部土体类型为砂质土";
                    break;
                case "C":
                    soilLooseness = "上部土体类型为碎石土";
                    break;
                default:
                    soilLooseness = "上部土体类型为未知土质"; // 默认情况
                    break;
            }
            String overlyingSoilType = "A".equals(gmSlopeData.getOverlyingSoilType()) ? "土层呈密实状态" : "土层呈松散状态";
            String rockChar = "A".equals(gmSlopeData.getRockCharacteristics()) ?
                    "下部出露强风化岩层，岩层无顺向结构面及软弱夹层" : "下部出露强风化岩层，岩层有顺向结构面及软弱夹层";
            String fractureDev = "A".equals(gmSlopeData.getFractureDevelopmentDegree()) ?
                    "节理裂隙不发育" : "节理裂隙发育";
            content = soilLooseness + "，" + overlyingSoilType + "；" + rockChar + "，" + fractureDev;

        }
        System.out.println("gmSlopeData.getHouseSlopePhoto() = " + gmSlopeData.getHouseSlopePhoto());
        dataMap.putIfAbsent("content", content);
        if(oConvertUtils.isEmpty(gmSlopeData.getHouseSlopePhoto())|| gmSlopeData.getHouseSlopePhoto().isEmpty()){
            System.out.println(1);
            dataMap.putIfAbsent("slopePhoto", "未上传边坡图片");
        }else {
             imagesCount = gmSlopeData.getHouseSlopePhoto().split(",").length;
            imageMap.putIfAbsent("slopePhoto",gmSlopeData.getHouseSlopePhoto());
            System.out.println(2);
        }
//        int j=1/0;

//        获取measure的数据进行处理（判断有几个方案至少一个最多2个）
//        gmSlopeData.getMeasure()=1938423694758580228,1938423694766968834
        //获取measure的数据进行处理（判断有几个方案至少一个最多2个）
        LambdaQueryWrapper<GmTreatmentMeasures> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(GmTreatmentMeasures::getId,Arrays.asList(gmSlopeData.getMeasure().split(",")));
        List<GmTreatmentMeasures> gmTreatmentMeasures = gmTreatmentMeasuresService.list(queryWrapper);

//        System.out.println("gmTreatmentMeasures.size() = " + gmTreatmentMeasures.size());
//        if (gmTreatmentMeasures.size() == 1) {
//            dataMap.putIfAbsent("measureDetail", gmTreatmentMeasures.get(0).getDetail());
//            imageMap.putIfAbsent("measureImage", gmTreatmentMeasures.get(0).getImage());
//            //大样图
//            imageMap.putIfAbsent("BigImage", gmTreatmentMeasures.get(0).getImage1());
//        } else if (gmTreatmentMeasures.size() == 2) {
//            dataMap.putIfAbsent("measureDetail", gmTreatmentMeasures.get(0).getDetail());
//            imageMap.putIfAbsent("measureImage", gmTreatmentMeasures.get(0).getImage());
//            dataMap.putIfAbsent("measureDetail2", gmTreatmentMeasures.get(1).getDetail());
//            imageMap.putIfAbsent("measureImage2", gmTreatmentMeasures.get(1).getImage());
//            imageMap.putIfAbsent("BigImage", gmTreatmentMeasures.get(0).getImage1() + "," + gmTreatmentMeasures.get(1).getImage1());
//        }

        // 安全处理措施数据
        List<String> bigImages = new ArrayList<>();
        System.out.println("gmTreatmentMeasures.size() = " + gmTreatmentMeasures.size());
        for (int i = 0; i < Math.min(2, gmTreatmentMeasures.size()); i++) {
            GmTreatmentMeasures measure = gmTreatmentMeasures.get(i);
            if (measure == null) continue;

            // 处理第一个措施
            if (i == 0) {
                Optional.ofNullable(measure.getDetail()).ifPresent(d -> dataMap.put("measureDetail", d));
                Optional.ofNullable(measure.getImage()).ifPresent(img -> imageMap.put("measureImage", img));
                imagesCount += measure.getImage() == null ? 0 : 1;
                System.out.println("imagesCount1 = " + imagesCount);
            }
            // 处理第二个措施
            else {
                Optional.ofNullable(measure.getDetail()).ifPresent(d -> dataMap.put("measureDetail2", d));
                Optional.ofNullable(measure.getImage()).ifPresent(img -> imageMap.put("measureImage2", img));
                imagesCount += measure.getImage() == null ? 0 : 1;
                System.out.println("imagesCount2 = " + imagesCount);
            }

            // 收集大样图
            Optional.ofNullable(measure.getImage1())
                    .filter(img -> !img.trim().isEmpty())
                    .ifPresent(bigImages::add);

//            System.out.println("imagesCount3 = " + imagesCount);
        }


// 设置BigImage
        if (!bigImages.isEmpty()) {
            imageMap.put("BigImage", String.join(",", bigImages));
            Object bigImageObj = imageMap.get("BigImage");
            if (bigImageObj != null) {
                //先使用逗号分隔，再一个一个插入
                String[] imageUrls = ((String) imageMap.get("BigImage")).split(",");
                //去除相同的图片路径
                imageUrls = Arrays.stream(imageUrls).distinct().toArray(String[]::new);
                imagesCount += imageUrls.length;
            }
        }
    return imagesCount ;
    }

//    /**
//     * 14 普通字典的翻译，根据多个dictCode和多条数据，多个以逗号分割
//     * @param dictCodes 例如：user_status,sex
//     * @param keys 例如：1,2,0
//     * @return
//     */
//    Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);

    private Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys) {
        return sysBaseAPI.translateManyDict(dictCodes, keys);
    }

    private String queryDictItemsByCode(String code, String value) {
        List<DictModel> slopeType = sysBaseAPI.queryDictItemsByCode(code);
        for (DictModel dictModel : slopeType) {
            if (dictModel.getValue().equals(value)) {
                return dictModel.getText();
            }
        }
        return "null";
    }
//    private static final String ERROR_IMAGE_URL = "http://123.56.202.52:19000/gmslopedata/image/20250714/1752464381066-图片加载失败.png1";




    public byte[] generateWordDocumentBytes(String templatePath, Map<String, Object> dataMap,
                                            Map<String, Object> imageMap, int imagesCount) throws Exception {

        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
//        System.out.println("imageMap = " + imageMap);
        // 加载模板文件
        FileInputStream fis = new FileInputStream(templatePath);
//        InputStream fis = new ClassPathResource(templatePath).getStream();
//        InputStream resource = getClass().getClassLoader().getResourceAsStream(templatePath);
        XWPFDocument document = new XWPFDocument(fis);

        // 替换普通字段
        replacePlaceholders(document, dataMap);
        // 定义错误图片URL常量

// 修改后的图片处理逻辑
//        if (imageMap.containsKey("slopePhoto")) {
//            String[] imageUrls = ((String) imageMap.get("slopePhoto")).split(",");
//            imageUrls = Arrays.stream(imageUrls).distinct().toArray(String[]::new);
//            String imageUrl =imageUrls[0];
//            byte[] imageData1 = downloadImageWithFallback(imageUrl, "切坡图片");
//            insertImageAtPlaceholder(document, "{{slopePhoto}}", imageData1, "切坡图片");
//            if(imageUrls.length>1){
//                for (int i = 1; i < imageUrls.length; i++) {
//                    byte[] imageData = downloadImageWithFallback(imageUrls[i], "切坡图片"+i);
//                    insertImageAtPlaceholder(document, "{{slopePhoto" + i + "}}", imageData, "切坡图片"+i);
//                }
//            }
//        }
        int completeCount = 0;

        if (imageMap.containsKey("slopePhoto")) {

            String[] imageUrls = ((String) imageMap.get("slopePhoto")).split(",");
//            System.out.println("imageUrls = " + imageUrls[0]);

            String[] distinctUrls = Arrays.stream(imageUrls)
                    .filter(url -> url != null && !url.trim().isEmpty())
                    .distinct()
                    .toArray(String[]::new);

            // 准备图片数据和说明文字
            List<byte[]> imageDataList = new ArrayList<>();
            List<String> captions = new ArrayList<>();

            for (int i = 0; i < distinctUrls.length; i++) {
                String caption = (i == 0) ? "切坡图片" : "";
                completeCount = completeCount+1;
                JSONObject obj = new JSONObject();
                obj.put(WebsocketConst.MSG_CMD, "exportWord");
                obj.put(WebsocketConst.MSG_TXT,imagesCount+","+completeCount);
                webSocket.sendMessage(loginUser.getId(),obj.toString());
                byte[] imageData = downloadImageWithFallback(distinctUrls[i], caption);
                imageDataList.add(imageData);
                captions.add(caption);
            }
//            if(captions.size()>=1){
            insertImagesAtPlaceholder(document, "{{slopePhoto}}", imageDataList, captions);
//            }
            // 批量插入所有图片

        }



        if (imageMap.containsKey("measureImage")) {
//            System.out.println("measureImage=" + imageMap.get("measureImage"));
            String imageUrl = (String) imageMap.get("measureImage");
            completeCount = completeCount+1;
            JSONObject obj = new JSONObject();
            obj.put(WebsocketConst.MSG_CMD, "exportWord");
            obj.put(WebsocketConst.MSG_TXT,imagesCount+","+completeCount);
            webSocket.sendMessage(loginUser.getId(),obj.toString());

            byte[] imageData = downloadImageWithFallback(imageUrl, "推荐设计方案1剖面图");
            insertImageAtPlaceholder(document, "{{measureImage}}", imageData, "推荐设计方案1工程布置剖面图");
        }
        if (imageMap.containsKey("measureImage2")) {
//            System.out.println("measureImage2=" + imageMap.get("measureImage2"));
            String[] imageUrls = ((String) imageMap.get("measureImage2")).split(",");
            String firstImageUrl = imageUrls.length > 0 ? imageUrls[0] : "";
            completeCount = completeCount+1;
            JSONObject obj = new JSONObject();
            obj.put(WebsocketConst.MSG_CMD, "exportWord");
            obj.put(WebsocketConst.MSG_TXT,imagesCount+","+completeCount);
            webSocket.sendMessage(loginUser.getId(),obj.toString());

            byte[] imageData = downloadImageWithFallback(firstImageUrl, "推荐设计方案2剖面图");
            insertImageAtPlaceholder1(document, "{{measureImage2}}", imageData, "推荐设计方案2工程布置剖面图",
                    (String) dataMap.get("measureDetail2"));
        }


//        if (imageMap.containsKey("slopePhoto")) {
//            byte[] imageData = IOUtils.toByteArray(new URL((String) imageMap.get("slopePhoto")).openStream());
//            insertImageAtPlaceholder(document, "{{slopePhoto}}", imageData, "切坡图片");
//        }
//        if (imageMap.containsKey("measureImage")) {
//            System.out.println("measureImage=" + imageMap.get("measureImage"));
//            byte[] imageData = IOUtils.toByteArray(new URL((String) imageMap.get("measureImage")).openStream());
//            insertImageAtPlaceholder(document, "{{measureImage}}", imageData, "推荐设计方案1工程布置剖面图");
//        }
//        if (imageMap.containsKey("measureImage2")) {
//            System.out.println("measureImage2=" + imageMap.get("measureImage2"));
//            String[] imageUrls = ((String) imageMap.get("measureImage2")).split(",");
//            byte[] imageData = IOUtils.toByteArray(new URL(imageUrls[0]).openStream());
//            insertImageAtPlaceholder1(document, "{{measureImage2}}", imageData, "推荐设计方案2工程布置剖面图", (String) dataMap.get("measureDetail2"));
////            insertImageAtPlaceholder(document, "{{measureImage1}}", imageData, "推荐设计方案2工程布置剖面图");
//
//        }
        if (imageMap.containsKey("BigImage")) {
//            System.out.println("BigImage=" + imageMap.get("BigImage"));
            Object bigImageObj = imageMap.get("BigImage");
            if (bigImageObj != null) {
                //先使用逗号分隔，再一个一个插入
                String[] imageUrls = ((String) imageMap.get("BigImage")).split(",");
                //去除相同的图片路径
                imageUrls = Arrays.stream(imageUrls).distinct().toArray(String[]::new);
                for (int i = 0; i < imageUrls.length; i++) {
                    completeCount = completeCount+1;
                    JSONObject obj = new JSONObject();
                    obj.put(WebsocketConst.MSG_CMD, "exportWord");
                    obj.put(WebsocketConst.MSG_TXT,imagesCount+","+completeCount);
                    webSocket.sendMessage(loginUser.getId(),obj.toString());

//                byte[] imageData = IOUtils.toByteArray(new URL(imageUrls[i]).openStream());
                    byte[] imageData = downloadImageWithFallback(imageUrls[i], extractImageNameFromUrl(imageUrls[i]));
//                imageUrls[i] = imageUrls[i].substring(imageUrls[i].lastIndexOf("/") + 1);
                    insertImageAtPlaceholder(document, "{{BigImage" + i + "}}", imageData, extractImageNameFromUrl(imageUrls[i]));
//                    System.out.println("大样图名称：" + imageUrls[i]);
                }
                System.out.println("imagesCount = " + imagesCount);
                System.out.println("completeCount = " + completeCount);
            }
        }

        // 输出字节流
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        document.write(out);
        document.close();
        return out.toByteArray();
    }


    /**
     * 在指定占位符处批量插入多张图片和居中文字说明
     */
    private void insertImagesAtPlaceholder(XWPFDocument document, String placeholder,
                                           List<byte[]> imageDataList, List<String> captions) throws Exception {
        if (imageDataList == null || imageDataList.isEmpty() || captions == null
                || imageDataList.size() != captions.size()) {
            throw new IllegalArgumentException("图片数据和说明列表不能为空且长度必须一致");
        }

        // 查找占位符位置
        XWPFParagraph placeholderPara = null;
        for (XWPFParagraph para : document.getParagraphs()) {
            if (para.getText() != null && para.getText().contains(placeholder)) {
                placeholderPara = para;
                break;
            }
        }

        if (placeholderPara == null) {
            System.err.println("未找到占位符: " + placeholder);
            return;
        }

        System.out.println("找到了占位符：" + placeholder);

        // 清除占位符文本
        while (!placeholderPara.getRuns().isEmpty()) {
            placeholderPara.removeRun(0);
        }

        // 插入第一张图片
        XWPFParagraph currentImagePara = placeholderPara;
        insertSingleImage(document, currentImagePara, imageDataList.get(0), captions.get(0));

        // 获取第一张图片的说明段落
        XWPFParagraph currentCaptionPara = getNextParagraph(document, currentImagePara);

        // 插入后续图片
        for (int i = 1; i < imageDataList.size(); i++) {
            // 在说明段落后创建新的图片段落
            currentImagePara = document.insertNewParagraph(currentCaptionPara.getCTP().newCursor());

            // 插入图片
            insertSingleImage(document, currentImagePara, imageDataList.get(i), captions.get(i));

            // 获取新图片的说明段落
            currentCaptionPara = getNextParagraph(document, currentImagePara);
        }
    }

    /**
     * 在指定段落插入单张图片和说明
     */
    private void insertSingleImage(XWPFDocument document, XWPFParagraph imagePara,
                                   byte[] imageData, String caption) throws Exception {
        // 插入图片
        XWPFRun run = imagePara.createRun();
        run.addPicture(new ByteArrayInputStream(imageData),
                XWPFDocument.PICTURE_TYPE_JPEG,
                System.currentTimeMillis() + ".jpg",
                Units.toEMU(380), Units.toEMU(240));

        // 确保创建说明段落
        createCaptionParagraph(document, imagePara, caption);
    }

    /**
     * 创建说明文字段落
     */
    private void createCaptionParagraph(XWPFDocument document, XWPFParagraph imagePara, String caption) {
        // 获取图片段落在文档中的位置
        int imageParaPos = document.getPosOfParagraph(imagePara);

        // 创建说明段落
        XWPFParagraph captionPara;
        if (imageParaPos + 1 < document.getParagraphs().size()) {
            // 在图片段落后插入新段落
            XWPFParagraph nextPara = document.getParagraphs().get(imageParaPos + 1);
            captionPara = document.insertNewParagraph(nextPara.getCTP().newCursor());
        } else {
            // 在文档末尾创建新段落
            captionPara = document.createParagraph();
        }

        // 设置说明文字样式
        captionPara.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun captionRun = captionPara.createRun();
        captionRun.setText(caption);
//        captionRun.setBold(true);
        captionRun.setBold(false);
        captionRun.setFontSize(12);
    }

    /**
     * 获取指定段落的下一段落
     */
    private XWPFParagraph getNextParagraph(XWPFDocument document, XWPFParagraph para) {
        int pos = document.getPosOfParagraph(para);
        if (pos >= 0 && pos + 1 < document.getParagraphs().size()) {
            return document.getParagraphs().get(pos + 1);
        }
        return null;
    }

    private byte[] downloadImageWithFallback(String imageUrl, String imageName) {
        log.info("图片下载开始: {}", imageName);
        if (imageUrl == null || imageUrl.isEmpty()) {
            return generateFallbackImage("缺少图片URL");
        }

        // 统一处理URL：如果是HTTP/HTTPS开头则提取路径部分，否则保留原路径
        String processedPath;
        String lowerCaseUrl = imageUrl.toLowerCase();
        if (lowerCaseUrl.startsWith("http://") || lowerCaseUrl.startsWith("https://")) {
            // 移除协议和域名部分，只保留路径
            String[] parts = imageUrl.split("/");
            if (parts.length > 3) {
                processedPath = String.join("/", Arrays.copyOfRange(parts, 3, parts.length));
            } else {
                processedPath = "";  // 只有域名没有路径的情况
            }
        } else {
            // 非HTTP开头的URL直接使用
            processedPath = imageUrl;
        }

        // 拼接完整URL并处理斜杠问题
        String fullUrl;
        if (uploadPrefix.endsWith("/") && processedPath.startsWith("/")) {
            fullUrl = uploadPrefix + processedPath.substring(1);
        } else if (!uploadPrefix.endsWith("/") && !processedPath.startsWith("/") && !processedPath.isEmpty()) {
            fullUrl = uploadPrefix + "/" + processedPath;
        } else {
            fullUrl = uploadPrefix + processedPath;
        }

        try {
            URL url = new URL(fullUrl);
            URLConnection conn = url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(10000);

            try (InputStream inputStream = conn.getInputStream()) {
                return IOUtils.toByteArray(inputStream);
            }
        } catch (Exception e) {
            String errorMsg = String.format("图片下载失败: %s\n处理前URL: %s\n处理后URL: %s\n原因: %s",
                    imageName, imageUrl, fullUrl, e.getMessage());
            log.error(errorMsg, e);
            return generateFallbackImage("图片加载失败");
        }
    }



    /**
     * 生成内置错误提示图片
     */
    private byte[] generateFallbackImage(String errorMsg) {
        try {
            BufferedImage img = new BufferedImage(380, 240, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = img.createGraphics();

            // 绘制背景
            g.setColor(new Color(240, 240, 240)); // 浅灰色背景
            g.fillRect(0, 0, 380, 240);

            // 绘制错误边框
            g.setColor(Color.RED);
            g.setStroke(new BasicStroke(2));
            g.drawRect(10, 10, 360, 220);

            // 绘制错误文本
            g.setColor(Color.RED);
            g.setFont(new Font("微软雅黑", Font.BOLD, 16));

            FontMetrics fm = g.getFontMetrics();
            String[] lines = errorMsg.split("\n");
            int lineHeight = fm.getHeight();
            int startY = (240 - lineHeight * lines.length) / 2 + fm.getAscent();

            for (int i = 0; i < lines.length; i++) {
                int x = (380 - fm.stringWidth(lines[i])) / 2;
                g.drawString(lines[i], x, startY + (i * lineHeight));
            }

            // 添加感叹号
            g.setFont(new Font("微软雅黑", Font.BOLD, 48));
            g.drawString("!", 180, 150);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(img, "PNG", baos);
            return baos.toByteArray();
        } catch (Exception e) {
            System.err.println("生成错误图片失败: " + e.getMessage());
            // 终极回退方案：返回一个空数组
            return new byte[0];
        }
    }






    private void copyRunFormatting(XWPFRun source, XWPFRun target) {
        // 复制字体格式
        target.setFontFamily(source.getFontFamily());
        target.setFontSize(source.getFontSize());
//        target.setBold(source.isBold());
        //不需要加粗
        target.setBold(false);
        target.setItalic(source.isItalic());
        target.setUnderline(source.getUnderline());
        target.setColor(source.getColor());
        target.setTextPosition(source.getTextPosition());

        // 复制其他格式属性
        target.setStrike(source.isStrike());
        target.setSubscript(source.getSubscript());
//        target.setSuperscript(source.getSuperscript());
    }

    private void cleanupEmptyRuns(XWPFParagraph paragraph) {
        List<XWPFRun> runs = paragraph.getRuns();
        for (int i = runs.size() - 1; i >= 0; i--) {
            XWPFRun run = runs.get(i);
            String text = run.getText(0);
            if (text == null || text.isEmpty()) {
                paragraph.removeRun(i);
            }
        }
    }

    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{([^{}]+)}}");
    private void replacePlaceholders(XWPFDocument document, Map<String, Object> dataMap) {
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            List<XWPFRun> runs = paragraph.getRuns();
            if (runs == null || runs.isEmpty()) continue;

            // 1. 合并段落文本（解决跨run问题）
            StringBuilder paraText = new StringBuilder();
            List<XWPFRun> runList = new ArrayList<>(runs);
            for (XWPFRun run : runList) {
                String text = run.getText(0);
                paraText.append(text != null ? text : "");
            }
            // 2. 一次性替换所有占位符
            String originalText = paraText.toString();
            Matcher matcher = PLACEHOLDER_PATTERN.matcher(originalText);
            StringBuffer newText = new StringBuffer();
            while (matcher.find()) {
                String fullKey = matcher.group(0); // {{key}}
                String key = matcher.group(1);     // key
                Object value = dataMap.get(key);

                if (value != null && !(value instanceof byte[])) {
                    matcher.appendReplacement(newText, Matcher.quoteReplacement(value.toString()));
                } else {
                    // 保留未匹配的占位符
                    matcher.appendReplacement(newText, Matcher.quoteReplacement(fullKey));
                }
            }
            matcher.appendTail(newText);
            // 3. 仅当文本变化时更新段落
            if (!originalText.equals(newText.toString())) {
                // 清空原有 runs
                for (int i = runList.size() - 1; i >= 1; i--) {
                    paragraph.removeRun(i);
                }

                // 更新第一个 run 的文本
                XWPFRun firstRun = runList.get(0);
                firstRun.setText(newText.toString(), 0);
                firstRun.setBold(false); // 例如设置为加粗
                //设置为蓝色
//                firstRun.setColor("FF0000"); // 设置为红色
            }
        }
    }
    /**
     * 在指定占位符处插入图片和居中文字说明
     */
    private void insertImageAtPlaceholder(XWPFDocument document, String placeholder,
                                          byte[] imageData, String imageCaption) throws Exception {
        boolean inserted = false;

        for (int i = 0; i < document.getParagraphs().size(); i++) {
            XWPFParagraph paragraph = document.getParagraphs().get(i);
            String text = paragraph.getText();

            if (text != null && text.contains(placeholder)) {
                System.out.println("找到了占位符：" + placeholder);
                // 清除原有 Run（即清除占位符文本）
                while (paragraph.getRuns() != null && !paragraph.getRuns().isEmpty()) {
                    paragraph.removeRun(0);
                }
                // 插入图片到当前段落
                int format = XWPFDocument.PICTURE_TYPE_JPEG;
                XWPFRun run = paragraph.createRun();
                run.addPicture(new ByteArrayInputStream(imageData), format, System.currentTimeMillis() + ".jpg",
                        Units.toEMU(380), Units.toEMU(240));
                // 获取下一个段落的位置
                int nextParaPos = i + 1;
                // 在图片下方创建说明文字段落
                XWPFParagraph captionPara;
                if (nextParaPos < document.getParagraphs().size()) {
                    // 如果不是文档最后一段，在当前段落之后插入
                    XWPFParagraph nextParagraph = document.getParagraphs().get(nextParaPos);
                    captionPara = document.insertNewParagraph(nextParagraph.getCTP().newCursor());
                } else {
                    // 如果是最后一段，直接创建新段落
                    captionPara = document.createParagraph();
                }
                captionPara.setAlignment(ParagraphAlignment.CENTER);
                XWPFRun captionRun = captionPara.createRun();
                captionRun.setText(imageCaption);
                captionRun.setBold(false);
                captionRun.setFontSize(12);
                inserted = true;
                break;
            }
        }

        if (!inserted) {
            System.err.println("未找到占位符: " + placeholder + "，图片已添加到文档末尾");

            // 1. 先创建图片段落
            XWPFParagraph imagePara = document.createParagraph();
            XWPFRun imageRun = imagePara.createRun();
            imageRun.addPicture(new ByteArrayInputStream(imageData), XWPFDocument.PICTURE_TYPE_JPEG,
                    System.currentTimeMillis() + ".jpg", Units.toEMU(380), Units.toEMU(240));

            // 2. 在图片下方创建说明文字段落
            XWPFParagraph captionPara = document.createParagraph();
            captionPara.setAlignment(ParagraphAlignment.CENTER);
            XWPFRun captionRun = captionPara.createRun();
            captionRun.setText(imageCaption);
            captionRun.setBold(false);
            captionRun.setFontSize(12);
        }
    }



    /**
     * 在指定占位符处插入图片和居中文字说明
     */
    private void insertImageAtPlaceholder1(XWPFDocument document, String placeholder, byte[] imageData, String imageCaption, String detail) throws Exception {

        System.out.println("未找到占位符: " + placeholder + "，图片已添加到文档末尾");
        XWPFParagraph captionPara1 = document.createParagraph();
        captionPara1.setAlignment(ParagraphAlignment.LEFT);
        XWPFRun captionRun1 = captionPara1.createRun();
        captionRun1.setText("推荐方案2：");
        captionRun1.setBold(false);
        captionRun1.setFontSize(14);

        XWPFParagraph captionPara2 = document.createParagraph();
        captionPara2.setAlignment(ParagraphAlignment.LEFT);
        XWPFRun captionRun2 = captionPara2.createRun();
        captionRun2.setText(detail);
        captionRun2.setBold(false);
        captionRun2.setFontSize(12);

        // 如果没找到占位符，默认加到最后
        XWPFParagraph para = document.createParagraph();
        XWPFRun run = para.createRun();
        run.addPicture(new ByteArrayInputStream(imageData), XWPFDocument.PICTURE_TYPE_JPEG,
                System.currentTimeMillis() + ".jpg", Units.toEMU(380), Units.toEMU(240));
        XWPFParagraph captionPara = document.createParagraph();
        captionPara.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun captionRun = captionPara.createRun();
        captionRun.setText(imageCaption);
        captionRun.setBold(false);
        captionRun.setFontSize(12);

    }


    @Override
    public List<EvaluateCountDTO> countEvaluateResults(String county, String town, String createBy) {
        return gmSlopeDataMapper.countEvaluateResults(county, town, createBy);
    }


    /**
     * 计算 Y1, Y2, Y3，用户采集调用这个方法
     *
     * @param gmSlopeData
     * @return
     */
    public void calculateYValues(GmSlopeData gmSlopeData) {
        LambdaQueryWrapper<GmAlgorithmSetting> queryWrapper = new LambdaQueryWrapper<>();
        String algorithm = gmAlgorithmSettingService.getOne(queryWrapper).getAlgorithm();
        SlopeStableSetting slopeSetting = loadOrCacheConfig(CACHE_KEY_SLOPE, slopeStableSettingService);
        RockSlopeStableSetting rockSlopeSetting = loadOrCacheConfig(CACHE_KEY_ROCK_SLOPE, rockSlopeStableSettingService);
        SoilRockSlopeSetting soilRockSlopeSetting = loadOrCacheConfig(CACHE_KEY_SOIL_ROCK_SLOPE, soilRockSlopeSettingService);
        SlopeRiskEvaluationSetting riskSetting = loadOrCacheConfig(CACHE_KEY_SLOPE_RISK, slopeRiskEvaluationSettingService);
        GmHazardParameters hazardParameters = loadOrCacheConfig(CACHE_KEY_HAZARD_PARAMETERS, gmHazardParametersService);
        GmIndexAdjustment indexAdjustment = loadOrCacheConfig(CACHE_KEY_INDEX_ADJUSTMENT, gmIndexAdjustmentService);

        if (slopeSetting == null || rockSlopeSetting == null || soilRockSlopeSetting == null || riskSetting == null || hazardParameters == null || indexAdjustment == null) {
            throw new RuntimeException("部分配置未找到，请检查Redis缓存或数据库是否正确加载");
        }
        //深度学习算法
        if ("1".equals(algorithm)) {
            try {
                // 构建请求参数
                Map<String, String> predictData = buildPredictParams(gmSlopeData);
                JSONObject requestBody = new JSONObject();
                requestBody.putAll(predictData);
                // 调用外部预测服务
                String predictUrl = deepLearningUrl;
                // 发送POST请求
                String response = sendPostRequest(predictUrl, requestBody.toJSONString());
                // 解析响应
                JSONObject responseObject = JSONObject.parseObject(response);
                String result = responseObject.getString("预测稳定性");
                //边坡稳定性指数
                double y = 0.0;
                if ("欠稳定".equals(result)) {
                    y = 2.875;
                } else if ("不稳定".equals(result)) {
                    y = 3.625;
                }
                //如果预测结果为较不稳定或不稳定，则进行治理方案获取
                if ("欠稳定".equals(result) || "不稳定".equals(result)) {
                    getMeasure1(gmSlopeData);
                    // 计算风险评估 Y4
                    //边坡危险性指数
                    double y4 = Utils.calculateDangerY(riskSetting, gmSlopeData);
                    //边坡风险程度
                    Utils.calculateAndSetHazardLevels(y, y4, gmSlopeData, hazardParameters);
                }
                gmSlopeData.setResult(result);
                this.updateById(gmSlopeData);
            } catch (Exception e) {
                log.error("调用预测服务失败", e);
            }
        }
        //随机森林算法
        else if ("2".equals(algorithm)) {
            try {
                // 构建请求参数
                Map<String, String> predictData = buildPredictParams(gmSlopeData);
                JSONObject requestBody = new JSONObject();
                requestBody.putAll(predictData);
//                String jsonParam = requestBody.toJSONString();
//                System.out.println("最终发送给预测服务的 JSON 参数：" + jsonParam);
//                System.out.println("2222" );
                // 调用外部预测服务
                String predictUrl = randomForestUrl;

                // 发送POST请求
                String response = sendPostRequest(predictUrl, requestBody.toJSONString());
                // 解析响应
                JSONObject responseObject = JSONObject.parseObject(response);
                String result = responseObject.getString("预测稳定性");
                //边坡稳定性指数
                double y = 0.0;
                if ("欠稳定".equals(result)) {
                    y = 2.875;
                } else if ("不稳定".equals(result)) {
                    y = 3.625;
                }

                //如果预测结果为较不稳定或不稳定，则进行治理方案获取
                if ("欠稳定".equals(result) || "不稳定".equals(result)) {
                    getMeasure1(gmSlopeData);
                    // 计算风险评估 Y4
                    //边坡危险性指数
                    double y4 = Utils.calculateDangerY(riskSetting, gmSlopeData);
                    //边坡风险程度
                    Utils.calculateAndSetHazardLevels(y, y4, gmSlopeData, hazardParameters);
                }
                gmSlopeData.setResult2(result);
                this.updateById(gmSlopeData);
            } catch (Exception e) {
                log.error("调用预测服务失败", e);
            }
        }
        //线性回归算法
        else if ("3".equals(algorithm)) {
            try {
                Map<String, String> predictData = buildPredictParams(gmSlopeData);
                JSONObject requestBody = new JSONObject();
                requestBody.putAll(predictData);
//                String jsonParam = requestBody.toJSONString();
//                System.out.println("最终发送给预测服务的 JSON 参数：" + jsonParam);
//                System.out.println("2222" );
                // 调用外部预测服务
                String predictUrl = linearRegressionUrl;

                // 发送POST请求
                String response = sendPostRequest(predictUrl, requestBody.toJSONString());
                // 解析响应
                JSONObject responseObject = JSONObject.parseObject(response);
                double responseObjectDouble = responseObject.getDouble("预测稳定性");
                double result = responseObjectDouble/25;
                String riskLevel = getRiskLevel(result);
                //边坡稳定性指数
                double y = 0.0;
                if ("欠稳定".equals(riskLevel)) {
                    y = 2.875;
                } else if ("不稳定".equals(riskLevel)) {
                    y = 3.625;
                }

                //如果预测结果为较不稳定或不稳定，则进行治理方案获取
                if ("欠稳定".equals(riskLevel) || "不稳定".equals(riskLevel)) {
                    getMeasure1(gmSlopeData);
                    // 计算风险评估 Y4
                    //边坡危险性指数
                    double y4 = Utils.calculateDangerY(riskSetting, gmSlopeData);
                    //边坡风险程度
                    Utils.calculateAndSetHazardLevels(y, y4, gmSlopeData, hazardParameters);
                }
                gmSlopeData.setResult3(riskLevel);
                this.updateById(gmSlopeData);
            } catch (Exception e) {
                log.error("调用预测服务失败", e);
            }

        }
        else if ("4".equals(algorithm)) {
            try {
                Map<String, String> predictData = buildPredictParams(gmSlopeData);
                JSONObject requestBody = new JSONObject();
                requestBody.putAll(predictData);
//                String jsonParam = requestBody.toJSONString();
//                System.out.println("最终发送给预测服务的 JSON 参数：" + jsonParam);
//                System.out.println("2222" );
                // 调用外部预测服务
                String predictUrl1 = deepLearningUrl;
                String predictUrl2 = randomForestUrl;
                String predictUrl3 = linearRegressionUrl;

                // 发送POST请求
                String result = sendPostRequest1(predictUrl1, requestBody.toJSONString());
                System.out.println("result = " + result);
                String result2 = sendPostRequest1(predictUrl2, requestBody.toJSONString());
                System.out.println("result2 = " + result2);
                String score = sendPostRequest1(predictUrl3, requestBody.toJSONString());
                System.out.println("score = " + score);
                //将result2转化为double类型
                double score1 = 0.0;
                try {
                    score1 = Double.parseDouble(score);
                } catch (NumberFormatException e) {
                    log.warn("无法将score转换为double类型，使用默认值0.0，score值为: " + score);
                    score1 = 0.0;
                }
                double score2 = score1/25;
                String result3 = getRiskLevel(score2);
                double y = 0.0;
                if ("欠稳定".equals(result)) {
                        y = 2.875;
                } else if ("不稳定".equals(result)) {
                        y = 3.625;
                }
                //如果预测结果为较不稳定或不稳定，则进行治理方案获取
                if ("欠稳定".equals(result) || "不稳定".equals(result)) {
                        getMeasure1(gmSlopeData);
                        // 计算风险评估 Y4
                        //边坡危险性指数
                        double y4 = Utils.calculateDangerY(riskSetting, gmSlopeData);
                        //边坡风险程度
                        Utils.calculateAndSetHazardLevels(y, y4, gmSlopeData, hazardParameters);
                }

                gmSlopeData.setResult(result);
                gmSlopeData.setResult2(result2);
                gmSlopeData.setResult3(result3);
                this.updateById(gmSlopeData);
            } catch (Exception e) {
                log.error("调用预测服务失败", e);
            }

        }
    }

    /**
     * 构建预测服务的请求参数（统一处理null转空字符串）
     */
    private Map<String, String> buildPredictParams(GmSlopeData gmSlopeData) {
        Map<String, String> params = new HashMap<>();
        params.put("切坡类型", Objects.requireNonNullElse(gmSlopeData.getSlopeType(), ""));
        params.put("切坡高度", Objects.requireNonNullElse(gmSlopeData.getCutSlopeHeight(), ""));
        params.put("切坡坡度", Objects.requireNonNullElse(gmSlopeData.getCutSlopeGradient(), ""));
        params.put("坡面形态", Objects.requireNonNullElse(gmSlopeData.getCutSlopeShape(), ""));
        params.put("自然斜坡坡度", Objects.requireNonNullElse(gmSlopeData.getNaturalSlopeGradient(), ""));
        params.put("土层松散程度", Objects.requireNonNullElse(gmSlopeData.getSoilLooseness(), ""));
        params.put("土体类型", Objects.requireNonNullElse(gmSlopeData.getOverlyingSoilType(), ""));
        params.put("岩组类型", Objects.requireNonNullElse(gmSlopeData.getRockType(), ""));
        params.put("植被覆盖率", Objects.requireNonNullElse(gmSlopeData.getVegetationCoverage(), ""));
        params.put("已有治理措施", Objects.requireNonNullElse(gmSlopeData.getGovernanceMeasures(), ""));
        params.put("岩体特征", Objects.requireNonNullElse(gmSlopeData.getRockCharacteristics(), ""));
        params.put("裂隙发育程度", Objects.requireNonNullElse(gmSlopeData.getFractureDevelopmentDegree(), ""));
        return params;
    }
//        else if ("3".equals(algorithm)) {
//            Map<String, Double> yValues = new HashMap<>();
//            // 获取缓存中的配置
//            SlopeStableSetting slopeSetting = loadOrCacheConfig(CACHE_KEY_SLOPE, slopeStableSettingService);
//            RockSlopeStableSetting rockSlopeSetting = loadOrCacheConfig(CACHE_KEY_ROCK_SLOPE, rockSlopeStableSettingService);
//            SoilRockSlopeSetting soilRockSlopeSetting = loadOrCacheConfig(CACHE_KEY_SOIL_ROCK_SLOPE, soilRockSlopeSettingService);
//            SlopeRiskEvaluationSetting riskSetting = loadOrCacheConfig(CACHE_KEY_SLOPE_RISK, slopeRiskEvaluationSettingService);
//            GmHazardParameters hazardParameters = loadOrCacheConfig(CACHE_KEY_HAZARD_PARAMETERS, gmHazardParametersService);
//            GmIndexAdjustment indexAdjustment = loadOrCacheConfig(CACHE_KEY_INDEX_ADJUSTMENT, gmIndexAdjustmentService);
//
//            if (slopeSetting == null || rockSlopeSetting == null || soilRockSlopeSetting == null || riskSetting == null || hazardParameters == null || indexAdjustment == null) {
//                throw new RuntimeException("部分配置未找到，请检查Redis缓存或数据库是否正确加载");
//            }
//
//            // 根据 rockCharacteristics 决定使用哪个设置
//            //保留2位小数
//            double y = 0.0;
//            String slopeType = gmSlopeData.getSlopeType();
//            System.out.println("slopeType = " + slopeType);
//            if ("C".equals(slopeType)) {
//                // 计算土质边坡的 Y 值
//                y = Utils.calculateSlopeY(slopeSetting, gmSlopeData);
//            } else if ("A".equals(slopeType)) {
//                // 计算岩质边坡的 Y 值
//                y = Utils.calculateRockY(rockSlopeSetting, gmSlopeData);
//            } else if ("B".equals(slopeType)) {
//                // 计算土岩质边坡的 Y 值
//                y = Utils.calculateSoilRockY(soilRockSlopeSetting, gmSlopeData);
//            }


    /**
     * 计算 Y1, Y2, Y3，导入excel调用这个方法
     *
     * @param gmSlopeData
     * @return
     */
    public void calculateYValues1(GmSlopeData gmSlopeData) {
        LambdaQueryWrapper<GmAlgorithmSetting> queryWrapper = new LambdaQueryWrapper<>();
        String  algorithm = gmAlgorithmSettingService.getOne(queryWrapper).getAlgorithm();
        if ("1".equals(algorithm)) {
            Map<String, Double> yValues = new HashMap<>();
            // 获取缓存中的配置
            SlopeStableSetting slopeSetting = loadOrCacheConfig(CACHE_KEY_SLOPE, slopeStableSettingService);
            RockSlopeStableSetting rockSlopeSetting = loadOrCacheConfig(CACHE_KEY_ROCK_SLOPE, rockSlopeStableSettingService);
            SoilRockSlopeSetting soilRockSlopeSetting = loadOrCacheConfig(CACHE_KEY_SOIL_ROCK_SLOPE, soilRockSlopeSettingService);
            SlopeRiskEvaluationSetting riskSetting = loadOrCacheConfig(CACHE_KEY_SLOPE_RISK, slopeRiskEvaluationSettingService);
            GmHazardParameters hazardParameters = loadOrCacheConfig(CACHE_KEY_HAZARD_PARAMETERS, gmHazardParametersService);
            GmIndexAdjustment indexAdjustment = loadOrCacheConfig(CACHE_KEY_INDEX_ADJUSTMENT, gmIndexAdjustmentService);

            if (slopeSetting == null || rockSlopeSetting == null || soilRockSlopeSetting == null || riskSetting == null || hazardParameters == null || indexAdjustment == null) {
                throw new RuntimeException("部分配置未找到，请检查Redis缓存或数据库是否正确加载");
            }

            // 根据 rockCharacteristics 决定使用哪个设置
            //保留2位小数
            double y = 0.0;
            String slopeType = gmSlopeData.getSlopeType();
            System.out.println("slopeType = " + slopeType);

            if ("C".equals(slopeType)) {
                // 计算土质边坡的 Y 值
                y = Utils.calculateSlopeY(slopeSetting, gmSlopeData);
            } else if ("A".equals(slopeType)) {
                // 计算岩质边坡的 Y 值
                y = Utils.calculateRockY(rockSlopeSetting, gmSlopeData);
            } else if ("B".equals(slopeType)) {
                // 计算土岩质边坡的 Y 值
                y = Utils.calculateSoilRockY(soilRockSlopeSetting, gmSlopeData);
            }
//        System.out.println("y = " + y);
            //根据植被覆盖率、已有治理措施两项进行修正
            y = Utils.modifyY(y, indexAdjustment, gmSlopeData);
//        System.out.println("y3= " + y);
            String riskLevel = getRiskLevel(y);
            gmSlopeData.setResult(riskLevel);
            //进行危险性评价及相关治理方案获取
            if ((y >= 2.5)) {
                // 计算风险评估 Y4
                //边坡危险性指数
                double y4 = Utils.calculateDangerY(riskSetting, gmSlopeData);
                //边坡风险程度
                Utils.calculateAndSetHazardLevels(y, y4, gmSlopeData, hazardParameters);
                //相关治理方案获取
                getMeasure1(gmSlopeData);
            }
        }
        else if ("2".equals(algorithm)) {
            try {
                // 构建请求参数
                Map<String, String> predictData = new HashMap<>();
                predictData.put("切坡类型", gmSlopeData.getSlopeType());
//                System.out.println("gmSlopeData.getSlopeType() = " + gmSlopeData.getSlopeType());
                predictData.put("切坡类型", Objects.requireNonNullElse(gmSlopeData.getSlopeType(), ""));
                predictData.put("切坡高度", Objects.requireNonNullElse(gmSlopeData.getCutSlopeHeight(), ""));
                predictData.put("切坡坡度", Objects.requireNonNullElse(gmSlopeData.getCutSlopeGradient(), ""));
                predictData.put("坡面形态", Objects.requireNonNullElse(gmSlopeData.getCutSlopeShape(), ""));
                predictData.put("自然斜坡坡度", Objects.requireNonNullElse(gmSlopeData.getNaturalSlopeGradient(), ""));
                predictData.put("土层松散程度", Objects.requireNonNullElse(gmSlopeData.getSoilLooseness(), ""));
                predictData.put("土体类型", Objects.requireNonNullElse(gmSlopeData.getOverlyingSoilType(), ""));
                predictData.put("岩组类型", Objects.requireNonNullElse(gmSlopeData.getRockType(), ""));
                predictData.put("植被覆盖率", Objects.requireNonNullElse(gmSlopeData.getVegetationCoverage(), ""));
                // 重点处理可能为 null 的字段：将 null 转为 ""
                predictData.put("已有治理措施", Objects.requireNonNullElse(gmSlopeData.getGovernanceMeasures(), ""));
                predictData.put("岩体特征", Objects.requireNonNullElse(gmSlopeData.getRockCharacteristics(), ""));
                predictData.put("裂隙发育程度", Objects.requireNonNullElse(gmSlopeData.getFractureDevelopmentDegree(), ""));
                // 调用外部预测服务
                String predictUrl = "http://8.148.26.149:8001/predict";
                JSONObject requestBody = new JSONObject();
                requestBody.putAll(predictData);

                // 发送POST请求
                String response = sendPostRequest(predictUrl, requestBody.toJSONString());

                // 解析响应
                JSONObject responseObject = JSONObject.parseObject(response);
//                System.out.println("responseObject = " + responseObject);
                gmSlopeData.setResult(responseObject.getString("预测稳定性"));
                Map<String, Object> result = new HashMap<>();
            } catch (Exception e) {
                log.error("调用预测服务失败", e);
            }
        }

    }

    /**
     * 发送POST请求
     * @param url 请求地址
     * @param jsonBody 请求体JSON字符串
     * @return 响应内容
     * @throws Exception 请求异常
     */
    private String sendPostRequest(String url, String jsonBody) throws Exception {
        URL obj = new URL(url);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/json; utf-8");
        con.setRequestProperty("Accept", "application/json");
        con.setDoOutput(true);

        try (OutputStream os = con.getOutputStream()) {
            byte[] input = jsonBody.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(con.getInputStream(), "utf-8"))) {
            StringBuilder response = new StringBuilder();
            String responseLine = null;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
            return response.toString();
        }
    }
    private String sendPostRequest1(String url, String jsonBody) throws Exception {
        URL obj = new URL(url);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/json; utf-8");
        con.setRequestProperty("Accept", "application/json");
        con.setDoOutput(true);

        try (OutputStream os = con.getOutputStream()) {
            byte[] input = jsonBody.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(con.getInputStream(), "utf-8"))) {
            StringBuilder response = new StringBuilder();
            String responseLine = null;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
            // 解析响应
            JSONObject responseObject = JSONObject.parseObject(response.toString());
            String result = responseObject.getString("预测稳定性");
            return result;
        }
    }



    private void getMeasure1(GmSlopeData gmSlopeData) {
        LambdaQueryWrapper<GmTreatmentMeasures> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(GmTreatmentMeasures::getCutSlopeHeight, gmSlopeData.getCutSlopeHeight())
                .eq(GmTreatmentMeasures::getSlopeWallDistance, gmSlopeData.getSlopeWallDistance())
                .eq(GmTreatmentMeasures::getNaturalSlopeGradient, gmSlopeData.getNaturalSlopeGradient());
        if (gmSlopeData.getSlopeType().equals("A")) {
            //岩质
            if (gmSlopeData.getRockCharacteristics().equals("A")) {
                queryWrapper.eq(GmTreatmentMeasures::getType1, "1");
            } else {
                queryWrapper.eq(GmTreatmentMeasures::getType1, "2");
            }
        } else if (gmSlopeData.getSlopeType().equals("B")) {
            //土质
            queryWrapper.eq(GmTreatmentMeasures::getType1, "3");
        } else if (gmSlopeData.getSlopeType().equals("C")) {
            if (gmSlopeData.getSoilLooseness().equals("A")) {
                queryWrapper.eq(GmTreatmentMeasures::getType1, "4");
            } else {
                queryWrapper.eq(GmTreatmentMeasures::getType1, "5");
            }
        }
        List<GmTreatmentMeasures> list = gmTreatmentMeasuresService.list(queryWrapper);
        System.out.println("措施方案：" + list.size());
        if (oConvertUtils.isNotEmpty(list)) {
            String measureIds = list.stream()
                    .map(gmTreatmentMeasures -> gmTreatmentMeasures.getId().toString())
                    .collect(Collectors.joining(","));
            gmSlopeData.setMeasure(measureIds);
        }
    }

    /**
     * 危险等级判定逻辑：
     *
     * @param
     * @return 切坡稳定性等级    稳定	较稳定	较不稳定	不稳定
     * //			 稳定性指数Yi	1.75≤Yi	1.75＜Yi≤2.5	2.5＜Yi≤3.25	Yi＞3.25
     */
    private static String getRiskLevel(double hij) {
        if (hij >= 3.25) return "不稳定";
        else if (hij >= 2.5) return "欠稳定";
        else if (hij >= 1.75) return "基本稳定";
        else return "稳定";
    }

    /**
     * 尝试从 Redis 获取缓存，若不存在则查询数据库并写入缓存
     *
     * @param cacheKey 缓存键名
     * @param service  数据库服务
     * @return 配置对象
     */
    private <T> T loadOrCacheConfig(String cacheKey, IService<T> service) {
        T cached = (T) redisUtil.get(cacheKey);
        if (cached != null) {
            return cached;
        }
        T fromDb = service.getOne(new QueryWrapper<>());
        if (fromDb != null) {
            redisUtil.set(cacheKey, fromDb);
            return fromDb;
        }
        log.warn("数据库中未找到对应配置，缓存 key: {}", cacheKey);
        return null;
    }

    // 引入模板文件存放地址
    @Value(value = "${gm.SockType}")
    private String SockTypePath;

    public Map<String, Object> queryByPoint(double longitude, double latitude) throws Exception {

        System.out.println("longitude = " + longitude);
        System.out.println("latitude = " + latitude);


        // 设置字符编码为 GBK/GB18030，避免 DBF 文件中中文乱码
        System.setProperty("org.geotools.referencing.forceXY", "true");
        Charset charset = Charset.forName("GBK"); // 或 GB18030
        System.setProperty("dbf.encoding", charset.name());

        File file = new File(SockTypePath);
        if (!file.exists()) {
            log.error("文件不存在");
            return null;
        }

        Map<String, Object> map = new HashMap<>();
        map.putIfAbsent("url", file.toURI().toURL());
        map.putIfAbsent("charset", charset);
        Map<String, Object> resultMap = new HashMap<>();
        DataStore dataStore = null;
        try {
            System.out.println("11111");
            dataStore = DataStoreFinder.getDataStore(map);
            if (dataStore == null) {
                log.error("无法加载数据源，请检查路径和编码");
                return null;
            }
            String typeName = dataStore.getTypeNames()[0];
            SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);
            SimpleFeatureType schema = featureSource.getSchema();

            // 打印 CRS 信息（调试）
            CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326");
            CoordinateReferenceSystem targetCRS = schema.getCoordinateReferenceSystem();
            MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);

            GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
            Point point = geometryFactory.createPoint(new Coordinate(longitude, latitude));
            Point transformedPoint = (Point) JTS.transform(point, transform);

            String filterStr = "INTERSECTS(the_geom, POINT(" + transformedPoint.getX() + " " + transformedPoint.getY() + "))";
            Filter filter = ECQL.toFilter(filterStr);

            SimpleFeatureCollection features = featureSource.getFeatures(filter);


            try (SimpleFeatureIterator iterator = features.features()) {
                System.out.println("222 = " + 222);
                while (iterator.hasNext()) {
                    SimpleFeature feature = iterator.next();
                    // 获取字段值
                    System.out.println("222 = " + 333);
                    Object rockType = feature.getAttribute("岩类");
                    Object rockTypeName = feature.getAttribute("岩类名称");
                    Object descriptor = feature.getAttribute("DESCRIPTOR");
                    Object symbol = feature.getAttribute("SYMBOL");
                    System.out.println("岩类: " + (rockType != null ? rockType.toString() : "null"));
                    System.out.println("岩类名称: " + (rockTypeName != null ? rockTypeName.toString() : "null"));
                    System.out.println("DESCRIPTOR: " + (descriptor != null ? descriptor.toString() : "null"));
                    System.out.println("SYMBOL: " + (symbol != null ? symbol.toString() : "null"));
                    resultMap.putIfAbsent("岩类", rockType);
                    resultMap.putIfAbsent("岩类名称", rockTypeName);
                    resultMap.putIfAbsent("DESCRIPTOR", descriptor);
                    resultMap.putIfAbsent("SYMBOL", symbol);
                }
            }

        } catch (Exception e) {
            log.error("查询出错", e);
            e.printStackTrace();
        } finally {
            if (dataStore != null) {
                dataStore.dispose();
            }
        }
        return resultMap;
    }


    /**
     * 从图片URL中提取简洁的描述性名称
     * @param imageUrl 图片的完整URL地址
     * @return 提取后的名称（如"大样图-落石挡墙"）
     */
    public static String extractImageNameFromUrl(String imageUrl) {
        if (imageUrl == null || imageUrl.isEmpty()) {
            return "未命名";
        }

        // 1. 获取文件名部分（最后一个斜杠之后的内容）
        String fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1);

        // 2. 移除文件扩展名
        if (fileName.contains(".")) {
            fileName = fileName.substring(0, fileName.lastIndexOf('.'));
        }

        // 3. 移除时间戳前缀（13位数字 + 短横线）
        fileName = fileName.replaceFirst("^\\d{13}-", "");

        // 4. 可选：移除前缀中的"大样图-"（如果需要）
        // fileName = fileName.replaceFirst("^大样图-", "");

        // 5. 返回处理后的名称
        return fileName;
    }


}
